using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Collections;
using System.Reflection;
using System.Security.Permissions;
using System.ComponentModel;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
using System.Data;
using System.Data.Odbc;
using NumericalRecipes;
using FftLib;
using BmgNet;
using StaticUtility;

namespace ObjectLib
{
	[Serializable]
	public class FilterResponseAnimation : ISerializable, IDeserializationCallback
	{
		/// <summary>
		/// For each animation, filter response thresholds.
		/// </summary>
		public float[] Thresholds;
		/// <summary>
		/// For each animation, the proportion of claimed edge elements inside the ROI.
		/// </summary>
		public float[] PropClaimedRoi;
		/// <summary>
		/// For each animation, marginal value of PropClaimedRoi
		/// </summary>
		public float[] MargPropClaimedRoi;
		/// <summary>
		/// Calculates and returns the frame with a maximum PropClaimedRoi.
		/// </summary>
		public int BestFrame
		{
			get
			{
				if(PropClaimedRoi==null || PropClaimedRoi.Length==0)
					return -1;
				int output = 0;
				float maxProp = PropClaimedRoi[0];
				for(int i=1; i<PropClaimedRoi.Length; i++)
					if(PropClaimedRoi[i]>maxProp)
					{
						maxProp = PropClaimedRoi[i];
						output = i;
					}
				return output;
			}
		}
		public string FilterFilename;
		[NonSerialized]
		public CategoryNode Category;
		public string CategoryName;
		public float PathPrecision;
		public float DistanceThreshold;
		public PointF RoiCenter;
		public float RoiRadius, ExclusionRadius;
		public FilterResponseList Filters;
		public ArrayList Subthreshold=new ArrayList();
		public ArrayList SuperUnclaimed=new ArrayList();
		public ArrayList SuperClaimed=new ArrayList();
		public FilterResponseAnimation()
		{
		}
		public FilterResponseAnimation(string FilterFilename)
		{
			this.FilterFilename = FilterFilename;
			Filters = new FilterResponseList();
			Filters = FilterResponseList.DeserializeBinary(FilterFilename);
		}
		public FilterResponseAnimation(string FilterFilename, FilterResponseList Filters)
		{
			this.FilterFilename = FilterFilename;
			this.Filters = Filters;
		}
		//public Animation() {}
		public void OnPaint(PictureBox pb, RectangleF imgRect)
		{
		}
		public void NextFrame()
		{
		}
		public bool IsAnimation
		{
			get
			{
				return Thresholds.Length > 1;
			}
		}
		public bool IsSingleFrame
		{
			get
			{
				return Thresholds.Length == 1;
			}
		}
		/// <summary>
		/// Calculate the proportion of filters claimed in the ROI for each threshold.  Answer
		/// is given as the member variable PropClaimedRoi.
		/// </summary>
		public void CalcPropClaimedRoi()
		{
			FilterResponseList claimed, unclaimed;
			FilterResponse fr;

			PropClaimedRoi = new float[this.Thresholds.Length];
			MargPropClaimedRoi = new float[this.Thresholds.Length];

			float dx, dy, sqRad=RoiRadius*RoiRadius;
			int i,j,k, ctClaimed, ctUnclaimed;
			for(i=0; i<this.Thresholds.Length; i++)
			{
				claimed = (FilterResponseList)SuperClaimed[i];
				unclaimed = (FilterResponseList)SuperUnclaimed[i];
				ctClaimed=ctUnclaimed=0;
				for(j=0; j<claimed.Count; j++)
				{
					fr=claimed[j];
					dx=fr.Location.X-RoiCenter.X;
					dy=fr.Location.Y-RoiCenter.Y;
					if(dx*dx+dy*dy<=sqRad)
						ctClaimed++;
				}
				for(j=0; j<unclaimed.Count; j++)
				{
					fr=unclaimed[j];
					dx=fr.Location.X-RoiCenter.X;
					dy=fr.Location.Y-RoiCenter.Y;
					if(dx*dx+dy*dy<=sqRad)
						ctUnclaimed++;
				}
				if(ctUnclaimed+ctClaimed>0)
					PropClaimedRoi[i] = (float)ctClaimed/(float)(ctClaimed+ctUnclaimed);
				else
					PropClaimedRoi[i] = 0.0f;
			}
			if(this.Thresholds.Length==1)
				this.MargPropClaimedRoi[0]=0.0f;
			else
			{
				//	First
				i=j=0;
				k=1;
				this.MargPropClaimedRoi[i]=
					(PropClaimedRoi[k]-PropClaimedRoi[j])
					/ (Thresholds[k]-Thresholds[j]);
				//	Last
				i=k=Thresholds.Length-1;
				j=k-1;
				this.MargPropClaimedRoi[i]=
					(PropClaimedRoi[k]-PropClaimedRoi[j])
					/ (Thresholds[k]-Thresholds[j]);
				//	Middle
				for(i=1; i<this.Thresholds.Length-1; i++)
				{
					j=i-1;
					k=i+1;
					this.MargPropClaimedRoi[i]=
						(PropClaimedRoi[k]-PropClaimedRoi[j])
						/ (Thresholds[k]-Thresholds[j]);
				}
			}
		}
		public void ConstructAnimation(float[] Thresholds, CategoryNode Category, 
			Util.ProgressUpdater UpdateDelegate, float PathPrecision, float DistanceThreshold, 
			PointF RoiCenter, float RoiRadius, float ExclusionRadius)
		{
			this.Thresholds = Thresholds;
			this.Category = Category;
			this.PathPrecision = PathPrecision;
			this.DistanceThreshold = DistanceThreshold;
			this.RoiCenter = RoiCenter;
			this.RoiRadius = RoiRadius;
			this.ExclusionRadius = ExclusionRadius;

			//	Clear existing animations
			Subthreshold.Clear();
			SuperUnclaimed.Clear();
			SuperClaimed.Clear();

			//	For updating progress
			this.UpdateDelegate = UpdateDelegate;
			Util.ProgressUpdater updater = new Util.ProgressUpdater(UpdateConstructAnimation);
			frameWidth = 1.0f/(float)Thresholds.Length;

			//	Master list of filters
			Filters = Filters.Constrain(
				0.0f, RoiCenter, ExclusionRadius, updater, null);

			//	For each frame
			for(frameCt=0; frameCt < Thresholds.Length; frameCt++)
			{
				//	For updating progress
				frameBegin = (float)frameCt/(float)Thresholds.Length;

				FilterResponseList sub = new FilterResponseList();
				FilterResponseList superClaimed = new FilterResponseList();
				FilterResponseList superUnclaimed = new FilterResponseList();

				//	Dispense with filters below threshold, but keep track of them.
				FilterResponseList super = Filters.Constrain(
					this.Thresholds[frameCt],
					RoiCenter, ExclusionRadius, updater, sub);

				//	Cache filters and process updates
				Category.CacheFiltersOntoEdges(super,
					DistanceThreshold, PathPrecision,
					superClaimed, superUnclaimed, updater );

				//	Store the results of this frame.
				Subthreshold.Add(sub);
				SuperUnclaimed.Add(superUnclaimed);
				SuperClaimed.Add(superClaimed);
			}

			//	Calculate the proportion of filters claimed in the ROI for each threshold.
			CalcPropClaimedRoi();
		}
		[NonSerialized]
		private int frameCt;
		[NonSerialized]
		private float frameBegin, frameWidth;
		[NonSerialized]
		public Util.ProgressUpdater UpdateDelegate;
		public void UpdateConstructAnimation(float progress)
		{
			UpdateDelegate(frameBegin+frameWidth*progress);
		}
		public void SerializeBinary(string fname)
		{
			CategoryName = Category.Path;
			IFormatter formatter = new BinaryFormatter();
			Stream stream;
			stream = new FileStream(fname, FileMode.Create, FileAccess.Write, FileShare.None);
			formatter.Serialize( stream, this );
			stream.Close();
		}
		/// <summary>
		/// The method that opens a .FilterResponseList file from binary format.
		/// </summary>
		/// <param name="filepath"></param>
		/// <returns></returns>
		public static FilterResponseAnimation DeserializeBinary(string filepath)
		{
			BinaryFormatter formatter = new BinaryFormatter();
			Stream stream = new FileStream(filepath, FileMode.Open, FileAccess.Read, FileShare.Read);
			FilterResponseAnimation obj = (FilterResponseAnimation)formatter.Deserialize(stream);
			stream.Close();
			return obj;
		}
		public FileRelativizor OpdFileRef;
		public FilterResponseAnimation(SerializationInfo info, StreamingContext context)
		{
			Thresholds=(float[])info.GetValue("Thresholds", typeof(float[]));
			FilterFilename=info.GetString("FilterFilename");
			CategoryName=info.GetString("CategoryName");
			PathPrecision=info.GetSingle("PathPrecision");
			DistanceThreshold=info.GetSingle("DistanceThreshold");
			RoiCenter=(PointF)info.GetValue("RoiCenter", typeof(PointF));
			RoiRadius=info.GetSingle("RoiRadius");
			Filters=(FilterResponseList)info.GetValue("Filters", typeof(FilterResponseList));
			//Subthreshold=(ArrayList)info.GetValue("Subthreshold", typeof(ArrayList));
			Subthreshold = new ArrayList((object[])info.GetValue("baseSub", typeof(object[])));
			//SuperClaimed=(ArrayList)info.GetValue("SuperClaimed", typeof(ArrayList));
			SuperClaimed = new ArrayList((object[])info.GetValue("baseC", typeof(object[])));
			//SuperUnclaimed=(ArrayList)info.GetValue("SuperUnclaimed", typeof(ArrayList));
			SuperUnclaimed = new ArrayList((object[])info.GetValue("baseU", typeof(object[])));
			OpdFileRef=(FileRelativizor)info.GetValue("OpdFileRef", typeof(FileRelativizor));
			try
			{
				PropClaimedRoi = (float[])info.GetValue("pcir", typeof(float[]));
				MargPropClaimedRoi = (float[])info.GetValue("mpcir", typeof(float[]));
			}
			catch
			{
				MargPropClaimedRoi = PropClaimedRoi = null;
			}
		}
		public void OnDeserialization(object sender)
		{
			if(this.PropClaimedRoi==null)
				CalcPropClaimedRoi();
		}
		public void GetObjectData(SerializationInfo info, StreamingContext context)
		{
			info.AddValue("Thresholds", Thresholds);
			info.AddValue("FilterFilename", FilterFilename);
			info.AddValue("CategoryName", CategoryName);
			info.AddValue("PathPrecision", PathPrecision);
			info.AddValue("DistanceThreshold", DistanceThreshold);
			info.AddValue("RoiCenter", RoiCenter);
			info.AddValue("RoiRadius", RoiRadius);
			info.AddValue("Filters", Filters);
			info.AddValue("baseSub", Subthreshold.ToArray());
			info.AddValue("baseC", SuperClaimed.ToArray());
			info.AddValue("baseU", SuperUnclaimed.ToArray());
			info.AddValue("OpdFileRef", OpdFileRef);
			info.AddValue("pcir", this.PropClaimedRoi);
			info.AddValue("mpcir", this.MargPropClaimedRoi);
		}

	}
	[Serializable]
	public class FilterResponseList : ArrayList, ISerializable
	{
		public FilterResponseList() : base() {}
		public void SerializeBinary(string fname)
		{
			IFormatter formatter = new BinaryFormatter();
			Stream stream;
			stream = new FileStream(fname, FileMode.Create, FileAccess.Write, FileShare.None);
			formatter.Serialize( stream, this );
			stream.Close();
		}
		/// <summary>
		/// The method that opens a .FilterResponseList file from binary format.
		/// </summary>
		/// <param name="filepath"></param>
		/// <returns></returns>
		public static FilterResponseList DeserializeBinary(string filepath)
		{
			BinaryFormatter formatter = new BinaryFormatter();
			Stream stream = new FileStream(filepath, FileMode.Open, FileAccess.Read, FileShare.Read);
			FilterResponseList obj = (FilterResponseList)formatter.Deserialize(stream);
			stream.Close();
			return obj;
		}
		public static FilterResponseList FromTextFile(string filename)
		{
			StreamReader sr = new StreamReader(filename);

			//	Read the first line.
			string line = sr.ReadLine();

			if(line==null)
				return null;

			//	Parse the header info from the first line.
			float cycPerDeg = Single.NaN;
			float pixPerDeg = Single.NaN;
			float maxLum = Single.NaN;
			int wsCt = 0;
			string[] ss = line.Split(' ', '\t', '\n');
			try
			{
				//	cycles per degree
				while(ss[wsCt] == "\t" || ss[wsCt] == "" || ss[wsCt] == " ")
					wsCt++;
				cycPerDeg = Convert.ToSingle(ss[wsCt++]);
				//	pixels per degree
				while(ss[wsCt] == "\t" || ss[wsCt] == "" || ss[wsCt] == " ")
					wsCt++;
				pixPerDeg = Convert.ToSingle(ss[wsCt++]);
				//	max luminance
				while(ss[wsCt] == "\t" || ss[wsCt] == "" || ss[wsCt] == " ")
					wsCt++;
				maxLum = Convert.ToSingle(ss[wsCt]);
			}
			catch
			{
				throw new Exception("Error reading header information in " + filename);
			}

			FilterResponseList output = new FilterResponseList();

			//	Read the body and fill the output.
			float tempSwap;
			int ct =0;
			line = sr.ReadLine();
			while(line!=null)
			{
				ct++;
				ss = line.Split(' ', '\t');
				wsCt = 0;
				try
				{
					//	Create the filter response and add it to the list.
					FilterResponse fr = new FilterResponse();
					fr.PixelsPerCycle = pixPerDeg/cycPerDeg;
					output.Add(fr);

					//	Fill the filter response with information.

					//	Start with the location.
					fr.Location = PointF.Empty;
					//	X
					while(ss[wsCt] == "\t" || ss[wsCt] == "" || ss[wsCt] == " ")
						wsCt++;
					fr.Location.X = Convert.ToSingle(ss[wsCt++])-1.0f;
					//	Y
					while(ss[wsCt] == "\t" || ss[wsCt] == "" || ss[wsCt] == " ")
						wsCt++;
					fr.Location.Y = Convert.ToSingle(ss[wsCt++])-1.0f;

					//	Spikes per section.
					while(ss[wsCt] == "\t" || ss[wsCt] == "" || ss[wsCt] == " ")
						wsCt++;
					fr.SpikesPerSecond = Convert.ToSingle(ss[wsCt++]);

					//	Filter orientation.
					while(ss[wsCt] == "\t" || ss[wsCt] == "" || ss[wsCt] == " ")
						wsCt++;
					fr.Orientation = Convert.ToSingle(ss[wsCt++]);
					//fr.DrawCos =  (float)Math.Cos((double)fr.Orientation);
					//fr.DrawSin = (float)Math.Sin((double)fr.Orientation);
					fr.DrawSin = (float)Math.Cos((double)fr.Orientation);
					fr.DrawCos = -(float)Math.Sin((double)fr.Orientation);

					//	First luminance.
					while(ss[wsCt] == "\t" || ss[wsCt] == "" || ss[wsCt] == " ")
						wsCt++;
					fr.HighLuminanceValue = Convert.ToSingle(ss[wsCt++])/255.0f*maxLum;

					//	Last luminance.
					while(ss[wsCt] == "\t" || ss[wsCt] == "" || ss[wsCt] == " ")
						wsCt++;
					fr.LowLuminanceValue = Convert.ToSingle(ss[wsCt])/255.0f*maxLum;

					//	Swap luminance values if necessary
					if(fr.LowLuminanceValue>fr.HighLuminanceValue)
					{
						tempSwap = fr.LowLuminanceValue;
						fr.LowLuminanceValue = fr.HighLuminanceValue;
						fr.HighLuminanceValue = tempSwap;
					}
				}
				catch
				{
					throw new Exception("Error reading information in " + filename
						+ " on line " + Convert.ToString(ct) + ".");
				}
				line = sr.ReadLine();
			}

			return output;
		}
		public new FilterResponse this[ int index ] 
		{
			get  
			{
				return( (FilterResponse) base[index] );
			}
			set  
			{
				base[index] = value;
			}
		}

		/// <summary>
		/// Returns the index of the closest filter response past a certain activation threshold.
		/// </summary>
		/// <param name="pf"></param>
		/// <param name="actThresh">Filter responses must be above this activation threshold.
		/// to be considered.</param>
		/// <param name="distMin"></param>
		/// <returns></returns>
		public int ClosestResponse(PointF pf, float actThresh, out double distMin)
		{
			if(this.Count>0)
			{
				//	Find the first comparator.
				int i=0;
				double dist;
				int iMin;
				FilterResponse fr;
				fr = this[i];
				//	Search for the appropriate value of i
				while(actThresh < fr.SpikesPerSecond)
				{
					i++;
					if(i>=this.Count)
					{
						//	Exit without any spikes reaching threshold, return dummy values.
						distMin = -1.0f;
						return -1;
					}
					fr = this[i];
				}
				//	Store the values at minimum.
				iMin = i;
				fr = this[i];
				distMin = (double)(fr.Location.X*fr.Location.X + fr.Location.Y*fr.Location.Y);
				for(i=i+1; i<this.Count; i++)
				{
					fr = this[i];
					//	Search for the appropriate value of i
					while(actThresh < fr.SpikesPerSecond)
					{
						i++;
						if(i>=this.Count)
						{
							//	Solution found
							distMin = Math.Sqrt(distMin);
							return iMin;
						}
						fr = this[i];
					}
					//	Compare distance to the minimum distance.
					dist = (double)(fr.Location.X*fr.Location.X + fr.Location.Y*fr.Location.Y);
					if(dist<distMin)
					{
						iMin = i;
						distMin = dist;
					}
				}
				//	Solution found
				distMin = Math.Sqrt(distMin);
				return iMin;
			}
			//	Only executed when this.Count==0.
			distMin = -1.0f;
			return -1;
		}
		public FilterResponseList Constrain
			(float spikeThresh, PointF pf, float radius,Util.ProgressUpdater updater,
			FilterResponseList remainder)
		{
			radius *= radius;
			PointF thispf;
			FilterResponse fr;
			float dx, dy, len=(float)this.Count;
			FilterResponseList output = new FilterResponseList();
			for(int i=0; i<this.Count; i++)
			{
				fr = this[i];
				thispf = fr.Location;
				dx = thispf.X-pf.X;
				dy = thispf.Y-pf.Y;
				if(dx*dx+dy*dy < radius && fr.SpikesPerSecond>=spikeThresh)
					output.Add(fr);
				else if(remainder!=null)
					remainder.Add(fr);
				updater( (float)i/len );
			}
			return output;
		}
		#region ISerializable Members

		public void GetObjectData(SerializationInfo info, StreamingContext context)
		{
			info.AddValue("baseArray", base.ToArray());
		}
		public FilterResponseList(SerializationInfo info, StreamingContext context) : base()
		{
			object[] baseArray = (object[])info.GetValue("baseArray", typeof(object[]));
			Capacity = baseArray.Length;
			AddRange(baseArray);
		}

		#endregion
	}
	/// <summary>
	/// A class that embodies the response of oriented contrast filters.
	/// </summary>
	[Serializable]
	public class FilterResponse : IComparable, ISerializable
	{
		/// <summary>
		/// The number of pixels per cycle that defines the filter.
		/// </summary>
		public float PixelsPerCycle;
		/// <summary>
		/// The location of the filter.
		/// </summary>
		public PointF Location;
		/// <summary>
		/// The number of spikes per second.
		/// </summary>
		public float SpikesPerSecond;
		/// <summary>
		/// The maximum response orientation of the filter.
		/// </summary>
		public float Orientation;
		/// <summary>
		/// The luminance value on the side of the filter where luminance is highest (Cd/m^2).
		/// </summary>
		public float HighLuminanceValue;
		/// <summary>
		/// The luminance value on the side of the filter where luminance is lowest (Cd/m^2).
		/// </summary>
		public float LowLuminanceValue;
		/// <summary>
		/// Distance to a hand-segmented edge.  This is a placeholder for information from
		/// the FullEdge class.
		/// </summary>
		[NonSerialized]
		public float DistanceToSegmentedEdge;
		/// <summary>
		/// The distance from the starting point of the FullEdge parent object to the location
		/// of the polygon that matdches this FilterResponse.  This is a placeholder for
		/// information from the FullEdge class.
		/// </summary>
		[NonSerialized]
		public float PathDistanceAlongSegmentedEdge=0.0f;
		/// <summary>
		/// For drawing the slant.
		/// </summary>
		[NonSerialized]
		public float DrawCos, DrawSin;
		/// <summary>
		/// Empty constructor.
		/// </summary>
		public FilterResponse() {}
		[NonSerialized]
		private static FilterResponse _fr;
		public int CompareTo(object obj)
		{
			_fr = (FilterResponse)obj;
			if(_fr.PathDistanceAlongSegmentedEdge<this.PathDistanceAlongSegmentedEdge)
				return 1;
			if(_fr.PathDistanceAlongSegmentedEdge>this.PathDistanceAlongSegmentedEdge)
				return -1;
			return 0;
		}
		public void GetObjectData(SerializationInfo info, StreamingContext context)
		{
			info.AddValue("ppc",PixelsPerCycle);
			info.AddValue("loc",Location);
			info.AddValue("sps",SpikesPerSecond);
			info.AddValue("or",Orientation);
			info.AddValue("hl",HighLuminanceValue);
			info.AddValue("ll",LowLuminanceValue);
		}
		public FilterResponse(SerializationInfo info, StreamingContext context) : base()
		{
			PixelsPerCycle=info.GetSingle("ppc");
			Location = (PointF)info.GetValue("loc", typeof(PointF));
			SpikesPerSecond=info.GetSingle("sps");
			Orientation=info.GetSingle("or");
			DrawCos= -(float)Math.Sin(Orientation);
			DrawSin=  (float)Math.Cos(Orientation);
			HighLuminanceValue = info.GetSingle("hl");
			LowLuminanceValue = info.GetSingle("ll");
		}
	}
}